package com.strato.base.osgi.v1_0_0;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.wiring.BundleRevision;

import com.strato.ncache.v2_6_5.DataLoader;
import com.strato.ncache.v2_6_5.NCache;

public class BundleUtil {

	public static String getBundleVersion(Bundle bundle){
		return bundle.getHeaders().get("Bundle-Version");
	}
	
	@SuppressWarnings("unchecked")
	public static void exportServices(BundleContext bundleContext,Object object,Class<? extends Object>[] interfaces,Map<String,Object> props){
		String version=getBundleVersion(bundleContext.getBundle());
		Hashtable<String, Object> ht=new Hashtable<String,Object>();
		if(props!=null){
			ht.putAll(props);
		}
		ht.put("version", version);
		if(interfaces==null){
			List<Class<? extends Object>> interfaceList = getInterfaces(object.getClass());
			interfaces=interfaceList.toArray(new Class[interfaceList.size()]);
		}
		String[] intfNames=new String[interfaces.length];
		for(int i=0,len=interfaces.length;i<len;i++){
			intfNames[i]=interfaces[i].getName();
		}
		bundleContext.registerService(intfNames, object, ht);
		System.out.println("Exported service of "+object.getClass()+" as "+Arrays.toString(intfNames)+"("+ht+")");
	}
	
	private static List<Class<? extends Object>> getInterfaces(Class<? extends Object> clazz) {
		List<Class<? extends Object>> interfaces=new ArrayList<Class<? extends Object>>();
		interfaces.addAll(Arrays.asList(clazz.getInterfaces()));
		if(clazz.getSuperclass()!=null&&!clazz.getSuperclass().equals(Object.class)){
			interfaces.addAll(getInterfaces(clazz.getSuperclass()));
		}
		return interfaces;
	}

	public static <T> T getCachedService(BundleContext bundleContext,Class<T> intf,String query){
		List<T> services = getCachedServices(bundleContext, intf, query);
		if(services!=null&&!services.isEmpty()){
			return services.get(0);
		}
		return null;
	}
	public static <T> T getService(BundleContext bundleContext,Class<T> intf,String query){
		List<T> services = getServices(bundleContext, intf, query);
		if(services!=null&&!services.isEmpty()){
			return services.get(0);
		}
		return null;
	}
	
	private static Map<Object, NCache<?>> serviceCaches=new WeakHashMap<Object, NCache<?>>();

	public static <T> List<T> getCachedServices(BundleContext bundleContext,Class<T> intf,String query){
		String bundleName=bundleContext.getBundle().getSymbolicName();
		@SuppressWarnings("unchecked")
		NCache<List<T>> cache=(NCache<List<T>>) serviceCaches.get(bundleName);
		if(cache==null){
			cache=NCache.newInstance();
			serviceCaches.put(bundleName, cache);
		}
		String key=intf.getName()+"#"+query;
		List<T> data = cache.get(key, new DataLoader<List<T>>() {
			@Override
			public List<T> load() throws Exception {
				return getServices(bundleContext,intf,query);
			}
		});
		return data;
	}
	
	public static <T> List<T> getServices(BundleContext bundleContext,Class<T> intf,String query){
		try {
			Collection<ServiceReference<T>> srs = bundleContext.getServiceReferences(intf, query);
			if(srs==null){
				return null;
			}
			List<T> list=new ArrayList<T>();
			for(ServiceReference<T> sr:srs){
				T service=bundleContext.getService(sr);
				list.add(service);
			}
			return list;
		} catch (InvalidSyntaxException e) {
			throw new RuntimeException(e);
		}
	}


	private static Map<String,Class<?>> primitives=new HashMap<String,Class<?>>();
	static{
		primitives.put("long", Long.TYPE);
		primitives.put("int", Integer.TYPE);
		primitives.put("char", Character.TYPE);
		primitives.put("double", Double.TYPE);
		primitives.put("float", Float.TYPE);
		primitives.put("short", Short.TYPE);
		primitives.put("byte", Byte.TYPE);
		primitives.put("boolean", Boolean.TYPE);
	}
	
	public static Class<?> loadClass(Bundle bundle, String className) throws ClassNotFoundException {
		if(primitives.containsKey(className)){
			return primitives.get(className);
		}
		return bundle.loadClass(className);
	}

	public static Bundle getBundle(String bundleName,BundleContext bundleContext) {
		if(bundleName==null){
			return null;
		}
		for(Bundle bundle:bundleContext.getBundles()){
			if(bundleName.equals(bundle.getSymbolicName())){
				return bundle;
			}
		}
		return null;
	}
		
	public static boolean isFragment(Bundle bundle) {
		BundleRevision revision = bundle.adapt(BundleRevision.class);
		return ((revision.getTypes() & BundleRevision.TYPE_FRAGMENT) > 0);
	}


	public static void startBundle(Bundle bundle) {
		startBundle(bundle, 20);
	}
	private static void startBundle(Bundle bundle,int trytimes) {
		try {
			bundle.start();
		} catch (BundleException e) {
			if(trytimes>0&&"Unable to acquire global lock for resolve.".equals(e.getMessage())){
				try {
					Thread.sleep(100);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				startBundle(bundle,trytimes-1);
			}else{
				e.printStackTrace();
			}
		}
	}
}
